import datetime
import unittest

def cached_response(rsp, duration = 300):
  """Set cache headers on a response object.

  This routine will set the Cache-Control: and Expires: HTTP headers to inform
  downstream caches/clients that this content is indeed cachable, sharable and
  can be considered so for a period of 'duration' seconds.

  @param {google.appengine.ext.webapp.Response} rsp  Response object
  @param {int} duration  Time this content should be cached for, in seconds.
  """
  now = datetime.datetime.utcnow()
  expires = now + datetime.timedelta(seconds=duration)

  rsp.headers['Cache-Control'] = 'public, max_age=%d' % int(duration)
  rsp.headers['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')


class MockResponse(object):
  """Mock google.appengine.ext.webapp.Response class, used for unit testing.
  """

  def __init__(self):
    self.headers = {}


class MockDatetime(object):
  """Mock datetime.datetime class for testing.

  Only datetime.datetime.utcnow() is mocked, everything else goes via the real
  datetime class.

  bst: Yes, I know this is somewhat heavyweight for testing.. but I don't want
  to pull in Mox or a similar framework just yet.
  """
  def __init__(self, existingDateTime):
    """Construct a mock datetime class.
 
    @param {datetime.datetime}  existingDateTime  The real datetime class.
    """
    self.dt = existingDateTime

  def __call__(self, *args, **kwArgs):
    """Invokes the mocked object's constructor."""
    return self.dt(*args, **kwArgs)

  def utcnow(self):
    """Mock utcnow() routine.

    @return {datetime.datetime}  Always returns 2011-01-01 00:00:00 UTC.
    """
    return self.dt(2011, 1, 1, 0, 0, 0)

  def __get__(self, a):
    """Fall back to a property of the mocked object if we do not override it.

    @param {string} a  Property name to fetch.
    """
    return self.dt.__get__(a)



class CachedResponseTests(unittest.TestCase):

  def testCachedResponse(self):
    """Test cached_response with a default duration parameter.

    Mock out datetime.datetime with one that returns a known UTC time, and
    verify the Cache-Control and Expires headers are set as expected.
    """
    oldDatetime = datetime.datetime
    datetime.datetime = MockDatetime(datetime.datetime)

    try:
      response = MockResponse()
      cached_response(response)

      self.assertEquals(len(response.headers), 2)
      assert('Cache-Control' in response.headers)
      assert('Expires' in response.headers)
      self.assertEquals('public, max_age=300', response.headers['Cache-Control'])
      self.assertEquals('Sat, 01 Jan 2011 00:05:00 GMT',
                        response.headers['Expires'])

    finally:
      datetime.datetime = oldDatetime

  def testCachedResponseDuration(self):
    """Test cached_response with an explicit duration.

    Mock out datetime.datetime with one that returns a known UTC time, and
    verify the Cache-Control and Expires headers are set as expected when
    cached_response is called with a number of durations.
    """

    TESTCASES = [(0, 'Sat, 01 Jan 2011 00:00:00 GMT'),
                 (-1, 'Fri, 31 Dec 2010 23:59:59 GMT'),
                 (300, 'Sat, 01 Jan 2011 00:05:00 GMT')]

    oldDatetime = datetime.datetime
    datetime.datetime = MockDatetime(datetime.datetime)

    try:
      for duration, expHeader in TESTCASES:
        response = MockResponse()
        cached_response(response, duration=duration)

        self.assertEquals(len(response.headers), 2)
        assert('Cache-Control' in response.headers)
        assert('Expires' in response.headers)
        self.assertEquals('public, max_age=%d' % duration,
                          response.headers['Cache-Control'])
        self.assertEquals(expHeader, response.headers['Expires'])

    finally:
      datetime.datetime = oldDatetime

if __name__ == '__main__':
  unittest.main()
